/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Room.h"
#include "MudObject.h"
#include "MudPlayer.h"
#include "Error.h"
#include "color.h"
#include "CmdVisitor.h"
#include "MudMobile.h"
#include "MudExit.h"
#include "StringBuilder.h"

#define FOREACH_OBJECT for (ObjectsConstIter iter = objects.begin(); iter != objects.end(); ++iter)
#define FOREACH_PLAYER for (PlayersConstIter iter = players.begin(); iter != players.end(); ++iter)

using namespace Chronos;
using namespace std;

Room::Room(const string& name_, const string& descr)
        : name(name_)
        , description(descr)
{
    generateDescription();
    ++count;
}


Room::~Room() {
    FOREACH_OBJECT { delete *iter; }
    --count;
}

unsigned int Room::count = 0;

const string& Room::getName() const { return name; }

const std::string& Room::getShortDescription() const { return shortDescription; }

const std::string& Room::getLongDescription() const { return longDescription; }

MudPlayer* Room::getFirstPlayer() const {
    if (players.empty()) return 0;
    return players.front();
}


void Room::generateDescription() const {
    StringBuilder exitDescription;
    exitDescription << " (";
    int exitCount = 0;
    FOREACH_OBJECT {
        MudObject* obj = *iter;
        if (!obj->isVisible()) continue;
        MudExit* customExit = TypeVisitor<MudExit>::getType(*obj);
        if (customExit != 0) {
            if (exitCount++ != 0) exitDescription << ", ";
            exitDescription << ANSI_YELLOW << customExit->getName() << ANSI_NORMAL;
        }
    }
    exitDescription << ")";
    if (exitCount == 0) exitDescription.clear();

    StringBuilder descriptionBuilder;
    descriptionBuilder << name << exitDescription << '\n';
    shortDescription = descriptionBuilder;
    descriptionBuilder << description << '\n';
    longDescription = descriptionBuilder;
}


void Room::addPlayer(MudPlayer& player, const std::string& msg) {
    if (msg != "") showText(msg);
    if (player.getOption("descr") == "brief") {
        player.write(shortDescription);
    } else {
        player.write(longDescription);
    }
    players.push_back(&player);
    addObject(&player);
}


void Room::removePlayer(MudPlayer& player, const string& msg) {
    PlayersIter iter = find(players.begin(), players.end(), &player);
    if (iter == players.end()) throw MudError("Room: cannot find player " + player.getName() + " in room " + name);
    players.erase(iter);
    removeObject(&player);
    if (msg != "") showText(msg);
}


void Room::addObject(MudObject* object) {
    objects.push_back(object);
    object->setRoom(this);

    MudExit* customExit = TypeVisitor<MudExit>::getType(*object);
    if (customExit != 0) generateDescription();

    MudMobile* mobile = TypeVisitor<MudMobile>::getType(*object);
    if (mobile != 0) notifyRoomListeners(*mobile);
}


void Room::removeObject(MudObject* object) {
    ObjectsIter iter = find(objects.begin(), objects.end(), object);
    if (iter == objects.end()) throw MudError("Room: object " + object->getName() + " not in room");
    objects.erase(iter);
    object->setRoom(0);

    MudExit* customExit = TypeVisitor<MudExit>::getType(*object);
    if (customExit != 0) generateDescription();
}


MudObject* Room::findObject(const std::string& name) const {
    FOREACH_OBJECT {
        if ((*iter)->matchesName(name)) return *iter;
    }
    return 0;
}


void Room::visitObjects(ObjectVisitor& visitor) {
    ObjectsIter iter = objects.begin();
    while (iter != objects.end()) {
        bool keep = visitor.handle(*iter);  //NOTE cannot remove Exits this way
        if (keep) ++iter;
        else iter = objects.erase(iter);
    }
}


void Room::showText(const std::string& text) {
    string msg = text + "\n";
    FOREACH_PLAYER { (*iter)->write(msg); }
}


void Room::showText(const std::string& text, const MudMobile& mobile1) {
    string msg = text + "\n";
    FOREACH_PLAYER {
        MudPlayer* player = (*iter);
        if (player != &mobile1) {
            (*iter)->write(msg);
        }
    }
}


void Room::showText(const std::string& text, const MudMobile& mobile1, const MudMobile& mobile2) {
    string msg = text + "\n";
    FOREACH_PLAYER {
        MudPlayer* player = (*iter);
        if (player != &mobile1 && player != &mobile2) (*iter)->write(msg);
    }
}


void Room::notifyRoomListeners(MudMobile& mobile) {
    ObjectsIter iter = objects.begin();
    while (iter != objects.end()) {
        if (*iter == &mobile) {
            ++iter;
            continue;
        }
        bool keep = (*iter)->handleRoomEvent(mobile, MUDMOBILE_ARRIVE);
        if (!keep) iter = objects.erase(iter);
        else ++iter;
    }
}

